#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using IPSim.Data;
using IPSim.Util;

namespace IPSim.Core
{
    public class TransportIPDataEventArgs : EventArgs
    {
        private readonly IIPData m_ipData;

        /// <summary>
        /// 
        /// </summary>
        public TransportIPDataEventArgs(IIPData ipData)
        {
            m_ipData = ipData;
        }

        /// <summary>
        /// Data encapsulated in event argument
        /// </summary>
        public IIPData IPData
        {
            get { return m_ipData; }
        }

    }

    public delegate void TransportIPDataEventHandler(object source, TransportIPDataEventArgs e);

    public class IPInterface
    {
        private IPAddress m_ipAddress;
        private IPAddress m_subnetMask;
        private IPAddress m_networkAddress;
        private Queue<IPPacket> m_ipBuffer;
        private EthernetInterface m_ethernetInterface;
        private Thread m_packetProcessorThread;
        private ARPInterface m_arpInterface;
        private readonly static Type declaringType = typeof(IPInterface);
        public event TransportIPDataEventHandler TransportIPData;

        /// <summary>
        /// Method that, when pulsed by monitor, take packet from IP Buffer, and call method that process it.
        /// </summary>
        private void PacketProcessorMethod()
        {
            IPPacket tempPacket;
            while (true)
            {
                lock (m_ipBuffer)
                {
                    tempPacket = null;
                    while (m_ipBuffer.Count == 0) Monitor.Wait(m_ipBuffer);
                    tempPacket = m_ipBuffer.Dequeue();
                }
                ProcessPacket(tempPacket);
            }
        }

        private void ProcessPacket(IPPacket packet)
        {
            Logger.log("[" + m_ipAddress + "] Primljen je IP Paket", LogLevel.INFO);
            packet.Data.SenderIPAddress = packet.SrcAddress;
            if(TransportIPData!=null)
                TransportIPData(this, new TransportIPDataEventArgs(packet.Data));
        }

        public void SendThread(Object ObjIPPacket)
        {
            IPPacket ipPacket = ObjIPPacket as IPPacket;
            Boolean packetSent = false;
            Int16 iteration = 0;
            while(!packetSent && (iteration<5))
            {
                MacAddress tempMacAddress = null;
                Logger.log("[" + m_ipAddress + "] Start procedure slanja IP paketa", LogLevel.INFO);
                if (m_arpInterface.LocalResolveRequest(ipPacket.SrcAddress, ipPacket.DestAddress,out tempMacAddress))
                {
                    Logger.log("[" + m_ipAddress + "] IP address (" + ipPacket.DestAddress + ") resolved, packet sent", LogLevel.INFO);
                    m_ethernetInterface.ReceivePacketFromUpperLevel(ipPacket, tempMacAddress);
                    packetSent = true;
                }
                else
                {
                    Logger.log("[" + m_ipAddress + "] IP address (" + ipPacket.DestAddress + ") not resolved, thread suspended", LogLevel.INFO);
                    iteration++;
                    Thread.Sleep(iteration * 1000);
                }
            }

        }

        /// <summary>
        /// Creates thread that resolve IP address and send a package to ethernet interface within a thread
        /// </summary>
        /// <param name="ipPacket">IP Package to send</param>
        public void SendEthernetData(IIPData iipData, IPAddress destIP, IPPacket.IPProtocol protocol, byte ttl)
        {
            
            (new Thread(new ParameterizedThreadStart(SendThread))).Start(new IPPacket(m_ipAddress,destIP,iipData,protocol,ttl));
        }

        public void SendEthernetData(IIPData iipData, IPAddress destIP, IPPacket.IPProtocol protocol)
        {
            SendEthernetData(iipData, destIP, protocol, 5);            
        }

        /// <summary>
        /// Medota koju poziva event od NetworkkLayer-a.
        /// proverava da li je paket tipa IP, i ako je namenjen tekucem IP Interfaceu, stavlja ga u queue.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void ReceiveDataFromEthernet(object source, TransportEthernetDataEventArgs e)
        {
            IPPacket tempPacket;
            if (e.EtherType == EthernetFrame.EtherTypes.IPv4)
            {
                tempPacket = e.EthernetData as IPPacket;
                if((tempPacket.DestAddress == m_ipAddress)||(tempPacket.DestAddress == IPAddress.BroadcastAddress))
                {
                    lock(m_ipBuffer)
                    {
                        m_ipBuffer.Enqueue(tempPacket);
                        Monitor.PulseAll(m_ipBuffer);
                    }
                }
            }
        }

        public IPInterface(EthernetInterface ethernetInterface, IPAddress ipAddress)
            : this(ethernetInterface, ipAddress, new IPAddress(0))
        { 
        }

        public IPInterface(EthernetInterface ethernetInterface, IPAddress ipAddress, IPAddress subnetMask)
        {
            m_ipBuffer = new Queue<IPPacket>();
            m_ipAddress = ipAddress;
            m_subnetMask = subnetMask;
            m_networkAddress = new IPAddress(ipAddress.Address & subnetMask.Address);
            m_arpInterface = new ARPInterface(ethernetInterface);
            m_ethernetInterface = ethernetInterface;
            m_ethernetInterface.TransportEthernetData += new TransportEthernetDataEventHandler(this.ReceiveDataFromEthernet);
            m_packetProcessorThread = new Thread(new ThreadStart(PacketProcessorMethod));
            m_packetProcessorThread.Start();
            InitializeArpCache();
            Logger.log("[" + ipAddress + "] IP Interface CREATED!", LogLevel.INFO);
        }

        public void InitializeArpCache()
        {
            Logger.log("[" + m_ipAddress + "] Inicijalno upisan u Arp cache( " + m_ipAddress + " , " + m_ethernetInterface.MacAddress + " )", LogLevel.INFO);
                m_arpInterface.UpdateCache(m_ethernetInterface.MacAddress, m_ipAddress);             
        }

        /// <summary>
        /// Kills interface thread
        /// </summary>
        public void StopInterface()
        {
            Logger.log("[" + m_ipAddress + "] IP interface stoping", LogLevel.INFO);
            if (m_packetProcessorThread != null)
            {
                m_packetProcessorThread.Abort();
            }
        }

        public IPAddress IPAddress
        {
            get
            {
                return m_ipAddress;
            }
            set
            {
                m_ipAddress = value;
                m_networkAddress = new IPAddress(m_ipAddress.Address & m_subnetMask.Address);
            }
        }

        public IPAddress SubnetMASK
        {
            get
            {
                return m_subnetMask;
            }
            set
            {
                m_subnetMask = value;
                m_networkAddress = new IPAddress(m_ipAddress.Address & m_subnetMask.Address);
            }
        }

        /*~IPInterface()
        {
            Logger.log("[" + m_ipAddress + "] IP interface stoping", LogLevel.INFO);
            if (m_packetProcessorThread != null)
            {
                m_packetProcessorThread.Abort();
            }
        }*/
    }
}
